﻿using System;
using System.Windows;

//
// Interpolators originally adapted by Kenny Young from "Custom Animation Sample" at http://msdn2.microsoft.com/en-us/library/ms771715.aspx
//
namespace Interpolations
{
    public abstract class Interpolation
    {
        public abstract double GetAlpha(double progress);
    }

    public class LinearInterpolation : Interpolation
    {
        public override double GetAlpha(double progress)
        {
            return progress;
        }
    }

    public abstract class EasedInterpolation : Interpolation
    {
        public EasedInterpolation()
        {
            EdgeBehavior = EdgeBehaviorEnum.EaseOut;
        }

        public EasedInterpolation(EdgeBehaviorEnum edgeBehavior)
        {
            EdgeBehavior = edgeBehavior;
        }

        public enum EdgeBehaviorEnum { EaseIn, EaseOut, EaseInOut }

        public EdgeBehaviorEnum EdgeBehavior { get; set; }

        protected abstract double GetEaseInAlpha(double progress);
        protected abstract double GetEaseOutAlpha(double progress);

        protected virtual double GetEaseInOutAlpha(double timeFraction)
        {
            double returnValue = 0.0;

            // we cut each effect in half by multiplying the time fraction by two and halving the distance.
            if (timeFraction <= 0.5)
            {
                returnValue = 0.5 * this.GetEaseInAlpha(timeFraction * 2);
            }
            else
            {
                returnValue = 0.5 + 0.5 * this.GetEaseOutAlpha((timeFraction - 0.5) * 2);
            }
            return returnValue;
        }

        public override double GetAlpha(double progress)
        {
            switch (this.EdgeBehavior)
            {
                case EdgeBehaviorEnum.EaseIn:
                    return this.GetEaseInAlpha(progress);
                case EdgeBehaviorEnum.EaseOut:
                    return this.GetEaseOutAlpha(progress);
                case EdgeBehaviorEnum.EaseInOut:
                default:
                    return this.GetEaseInOutAlpha(progress);
            }
        }
    }

    public class BounceInterpolation : EasedInterpolation
    {
        public BounceInterpolation() : this(2, 3, EdgeBehaviorEnum.EaseOut) { }

        public BounceInterpolation(int bounces, double bounciness, EdgeBehaviorEnum edgeBehavior)
            : base(edgeBehavior)
        {
            Bounces = bounces;
            Bounciness = bounciness;
        }

        public int Bounces { get; set; }

        public double Bounciness { get; set; }

        protected override double GetEaseOutAlpha(double timeFraction)
        {
            double returnValue = 0.0;

            // math magic: The cosine gives us the right wave, the timeFraction is the frequency of the wave, 
            // the absolute value keeps every value positive (so it "bounces" off the midpoint of the cosine 
            // wave, and the amplitude (the exponent) makes the sine wave get smaller and smaller at the end.
            returnValue = Math.Abs(Math.Pow((1 - timeFraction), this.Bounciness)
                          * Math.Cos(2 * Math.PI * timeFraction * this.Bounces));
            returnValue = 1 - returnValue;
            return returnValue;
        }

        protected override double GetEaseInAlpha(double timeFraction)
        {
            double returnValue = 0.0;
            // math magic: The cosine gives us the right wave, the timeFraction is the amplitude of the wave, 
            // the absolute value keeps every value positive (so it "bounces" off the midpoint of the cosine 
            // wave, and the amplitude (the exponent) makes the sine wave get bigger and bigger towards the end.
            returnValue = Math.Abs(Math.Pow((timeFraction), this.Bounciness)
                          * Math.Cos(2 * Math.PI * timeFraction * this.Bounces));
            return returnValue;
        }
    }

    public class ElasticInterpolation : EasedInterpolation
    {
        public ElasticInterpolation() : this(4, 3, EdgeBehaviorEnum.EaseOut) { }

        public ElasticInterpolation(double springiness, double oscillations, EdgeBehaviorEnum edgeBehavior)
            : base(edgeBehavior)
        {
            Springiness = springiness;
            Oscillations = oscillations;
        }

        public double Springiness { get; set; }

        public double Oscillations { get; set; }

        protected override double GetEaseOutAlpha(double timeFraction)
        {
            double returnValue = 0.0;

            // math magic: The cosine gives us the right wave, the timeFraction * the # of oscillations is the 
            // frequency of the wave, and the amplitude (the exponent) makes the wave get smaller at the end
            // by the "springiness" factor. This is extremely similar to the bounce equation.
            returnValue = Math.Pow((1 - timeFraction), this.Springiness)
                          * Math.Cos(2 * Math.PI * timeFraction * this.Oscillations);
            returnValue = 1 - returnValue;
            return returnValue;
        }

        protected override double GetEaseInAlpha(double timeFraction)
        {
            double returnValue = 0.0;
            // math magic: The cosine gives us the right wave, the timeFraction * the # of oscillations is the 
            // frequency of the wave, and the amplitude (the exponent) makes the wave get smaller at the beginning
            // by the "springiness" factor. This is extremely similar to the bounce equation. 
            returnValue = Math.Pow((timeFraction), this.Springiness)
                          * Math.Cos(2 * Math.PI * timeFraction * this.Oscillations);
            return returnValue;
        }
    }

    public class BackInterpolation : EasedInterpolation
    {
        public BackInterpolation() : this(.5, 2, EdgeBehaviorEnum.EaseOut) { }

        public BackInterpolation(double amplitude, double suppression, EdgeBehaviorEnum edgeBehavior)
            : base(edgeBehavior)
        {
            Amplitude = amplitude;
            Suppression = suppression;
        }

        public double Amplitude { get; set; }

        public double Suppression { get; set; }

        protected override double GetEaseOutAlpha(double timeFraction)
        {
            double returnValue = 0.0;
            double frequency = 0.5;

            // math magic: The sine gives us the right wave, the timeFraction * 0.5 (frequency) gives us only half 
            // of the full wave, the amplitude gives us the relative height of the peak, and the exponent makes the 
            // effect drop off more quickly by the "suppression" factor. 
            returnValue = Math.Pow((timeFraction), this.Suppression)
                          * this.Amplitude * Math.Sin(2 * Math.PI * timeFraction * frequency) + timeFraction;
            return returnValue;
        }

        protected override double GetEaseInAlpha(double timeFraction)
        {
            double returnValue = 0.0;
            double frequency = 0.5;

            // math magic: The sine gives us the right wave, the timeFraction * 0.5 (frequency) gives us only half 
            // of the full wave (flipped by multiplying by -1 so that we go "backwards" first), the amplitude gives 
            // us the relative height of the peak, and the exponent makes the effect start later by the "suppression" 
            // factor. 
            returnValue = Math.Pow((1 - timeFraction), this.Suppression)
                          * this.Amplitude * Math.Sin(2 * Math.PI * timeFraction * frequency) * -1 + timeFraction;
            return returnValue;
        }
    }

    public class ExponentialInterpolation : EasedInterpolation
    {
        public ExponentialInterpolation() : this(2, EdgeBehaviorEnum.EaseInOut) { }

        public ExponentialInterpolation(double power, EdgeBehaviorEnum edgeBehavior)
            : base(edgeBehavior)
        {
            Power = power;
        }

        public double Power { get; set; }

        protected override double GetEaseInAlpha(double timeFraction)
        {
            double returnValue = 0.0;

            // math magic: simple exponential growth
            returnValue = Math.Pow(timeFraction, this.Power);
            return returnValue;
        }

        protected override double GetEaseOutAlpha(double timeFraction)
        {
            double returnValue = 0.0;

            // math magic: simple exponential decay
            returnValue = Math.Pow(timeFraction, 1 / this.Power);
            return returnValue;
        }
    }
}
